package metricsdk

import (
	"bytes"
	"context"
	"errors"
	"strings"
	"sync"

	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/metric"
	"go.opentelemetry.io/otel/metric/unit"
)

var (
	defaultMeter metric.Meter
)

func SetMeterProvider(provider metric.MeterProvider, instrumentationName string, meterOpts ...metric.MeterOption) {
	defaultMeter = provider.Meter(instrumentationName, meterOpts...)
}

var (
	ErrLabelNamesLenNotMatch = errors.New("metrcisdk: label count not match")
	ErrInvalidMetricName     = errors.New("metrcisdk: invalid metric name")
)

// VecOpts 通用 Vec 初始化配置
type VecOpts struct {
	Name string
	Desc string
	Unit string
}

const sep = "#bk#"

type labelPair struct {
	Name  string
	Value string
}

type labelPairs []labelPair
type labelNames struct {
	mut    sync.Mutex
	names  []string
	labels map[string]labelPairs
}

func newLabelNames(names []string) *labelNames {
	return &labelNames{names: names, labels: map[string]labelPairs{}}
}

func (l *labelNames) Len() int {
	return len(l.names)
}

func (l *labelNames) Encode(lvs ...string) string {
	buf := &bytes.Buffer{}
	for i := 0; i < len(l.names); i++ {
		buf.WriteString(l.names[i])
		buf.WriteString("=")
		buf.WriteString(lvs[i])
		buf.WriteString(sep)
	}
	return buf.String()
}

func (l *labelNames) Decode(s string) labelPairs {
	l.mut.Lock()
	defer l.mut.Unlock()

	// 减少多次 Split 开销 不复制 Labels 对象
	v, ok := l.labels[s]
	if ok {
		return v
	}

	var labels labelPairs
	for _, pair := range strings.Split(s, sep) {
		kv := strings.SplitN(pair, "=", 2)
		if len(kv) != 2 {
			continue
		}
		labels = append(labels, labelPair{Name: kv[0], Value: kv[1]})
	}
	l.labels[s] = labels
	return l.labels[s]
}

type CommonVec interface {
	Name() string
	Desc() string
	Unit() string
}

type commonVec struct {
	name string
	desc string
	unit string
}

func (c *commonVec) Name() string { return c.name }
func (c *commonVec) Desc() string { return c.desc }
func (c *commonVec) Unit() string { return c.unit }

// upDownCounter 可实现 CounterVec / GaugeVec
type upDownCounter struct {
	commonVec
	mut        sync.Mutex
	labelNames *labelNames
	values     map[string]float64
	observer   metric.Float64CounterObserver
}

func (c *upDownCounter) inc(lvs ...string) error              { return c.add(1, lvs...) }
func (c *upDownCounter) dec(lvs ...string) error              { return c.add(-1, lvs...) }
func (c *upDownCounter) sub(val float64, lvs ...string) error { return c.add(-val, lvs...) }

func (c *upDownCounter) add(val float64, lvs ...string) error {
	if len(lvs) != c.labelNames.Len() {
		return ErrLabelNamesLenNotMatch
	}

	c.mut.Lock()
	defer c.mut.Unlock()

	c.values[c.labelNames.Encode(lvs...)] += val
	return nil
}

func (c *upDownCounter) set(val float64, lvs ...string) error {
	if len(lvs) != c.labelNames.Len() {
		return ErrLabelNamesLenNotMatch
	}

	c.mut.Lock()
	defer c.mut.Unlock()

	c.values[c.labelNames.Encode(lvs...)] = val
	return nil
}

func registerUpDownCounter(opts VecOpts, labelNames []string) (*upDownCounter, error) {
	if opts.Name == "" {
		return nil, ErrInvalidMetricName
	}

	c := &upDownCounter{
		labelNames: newLabelNames(labelNames),
		values:     map[string]float64{},
	}
	c.name = opts.Name
	c.desc = opts.Desc
	c.unit = opts.Unit

	var instOpts []metric.InstrumentOption
	if opts.Desc != "" {
		instOpts = append(instOpts, metric.WithDescription(opts.Desc))
	}
	if opts.Unit != "" {
		instOpts = append(instOpts, metric.WithUnit(unit.Unit(opts.Unit)))
	}
	observer, err := defaultMeter.NewFloat64CounterObserver(opts.Name, func(ctx context.Context, result metric.Float64ObserverResult) {
		c.mut.Lock()
		for k, v := range c.values {
			var attrs []attribute.KeyValue
			pairs := c.labelNames.Decode(k)
			for i := 0; i < len(pairs); i++ {
				attrs = append(attrs, attribute.String(pairs[i].Name, pairs[i].Value))
			}
			result.Observe(v, attrs...)
		}
		c.mut.Unlock()
	}, instOpts...)
	if err != nil {
		return nil, err
	}
	c.observer = observer
	return c, nil
}

// CounterVec

type CounterVec interface {
	CommonVec
	Inc(lvs ...string) error
	Add(v float64, lvs ...string) error
}

type CounterVecOpts VecOpts
type counterVec struct {
	*upDownCounter
}

func (c *counterVec) Inc(lvs ...string) error              { return c.inc(lvs...) }
func (c *counterVec) Add(val float64, lvs ...string) error { return c.add(val, lvs...) }

func MustRegisterCounterVec(opts CounterVecOpts, labelNames []string) CounterVec {
	c, err := RegisterCounterVec(opts, labelNames)
	if err != nil {
		panic(err)
	}
	return c
}

func RegisterCounterVec(opts CounterVecOpts, labelNames []string) (CounterVec, error) {
	c, err := registerUpDownCounter(VecOpts{
		Name: opts.Name, Desc: opts.Desc, Unit: opts.Unit,
	}, labelNames)
	if err != nil {
		return nil, err
	}
	return &counterVec{c}, nil
}

// GaugeVec

type GaugeVec interface {
	CommonVec
	Set(val float64, lvs ...string) error
	Inc(lvs ...string) error
	Dec(lvs ...string) error
	Add(val float64, lvs ...string) error
	Sub(val float64, lvs ...string) error
}

type GaugeVecOpts VecOpts
type gaugeVec struct {
	*upDownCounter
}

func (c *gaugeVec) Set(val float64, lvs ...string) error { return c.set(val, lvs...) }
func (c *gaugeVec) Inc(lvs ...string) error              { return c.inc(lvs...) }
func (c *gaugeVec) Dec(lvs ...string) error              { return c.dec(lvs...) }
func (c *gaugeVec) Add(val float64, lvs ...string) error { return c.add(val, lvs...) }
func (c *gaugeVec) Sub(val float64, lvs ...string) error { return c.sub(val, lvs...) }

func MustRegisterGaugeVec(opts GaugeVecOpts, labelNames []string) GaugeVec {
	c, err := RegisterGaugeVec(opts, labelNames)
	if err != nil {
		panic(err)
	}
	return c
}

func RegisterGaugeVec(opts GaugeVecOpts, labelNames []string) (GaugeVec, error) {
	c, err := registerUpDownCounter(VecOpts{
		Name: opts.Name, Desc: opts.Desc, Unit: opts.Unit,
	}, labelNames)
	if err != nil {
		return nil, err
	}
	return &gaugeVec{c}, nil
}

// HistogramVec

type HistogramVec interface {
	CommonVec
	Observe(val float64, lvs ...string) error
}

type HistogramVecOpts VecOpts
type histogramVec struct {
	commonVec
	labelNames []string
	histogram  metric.Float64Histogram
}

func (h *histogramVec) Observe(val float64, lvs ...string) error {
	if len(h.labelNames) != len(lvs) {
		return ErrLabelNamesLenNotMatch
	}

	var attrs []attribute.KeyValue
	for i := 0; i < len(h.labelNames); i++ {
		attrs = append(attrs, attribute.String(h.labelNames[i], lvs[i]))
	}
	h.histogram.Record(context.Background(), val, attrs...)
	return nil
}

func MustRegisterHistogramVec(opts HistogramVecOpts, labelNames []string) HistogramVec {
	c, err := RegisterHistogramVec(opts, labelNames)
	if err != nil {
		panic(err)
	}
	return c
}

func RegisterHistogramVec(opts HistogramVecOpts, labelNames []string) (HistogramVec, error) {
	if opts.Name == "" {
		return nil, ErrInvalidMetricName
	}

	h := &histogramVec{labelNames: labelNames}
	h.name = opts.Name
	h.desc = opts.Desc
	h.unit = opts.Unit

	var instOpts []metric.InstrumentOption
	if opts.Desc != "" {
		instOpts = append(instOpts, metric.WithDescription(opts.Desc))
	}
	if opts.Unit != "" {
		instOpts = append(instOpts, metric.WithUnit(unit.Unit(opts.Unit)))
	}

	var err error
	h.histogram, err = defaultMeter.NewFloat64Histogram(opts.Name, instOpts...)
	return h, err
}
